package com.tt.aile.search.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.internal.cglib.reflect.$FastConstructor;
import com.tt.aile.common.pojo.PageResult;
import com.tt.aile.item.pojo.*;
import com.tt.aile.item.pojo.bo.SpuBo;
import com.tt.aile.search.client.BrandClient;
import com.tt.aile.search.client.CategoryClient;
import com.tt.aile.search.client.GoodsClient;
import com.tt.aile.search.client.SpecificationClient;
import com.tt.aile.search.pojo.Goods;
import com.tt.aile.search.pojo.SearchRequset;
import com.tt.aile.search.pojo.SearchResult;
import com.tt.aile.search.repository.GoodsRespository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.lucene.queryparser.classic.XQueryParser;
import org.bouncycastle.math.raw.Nat;
import org.checkerframework.checker.units.qual.A;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.terms.InternalTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import sun.text.resources.FormatData;


import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 86176 shkstrat
 * @date 2020/11/18 - 15:39
 */
@Service
public class SearchService {

    @Autowired
    BrandClient brandClient;

    @Autowired
    GoodsClient goodsClient;

    @Autowired
    SpecificationClient specificationClient;

    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    GoodsRespository goodsRespository;

    @Autowired
    CategoryClient categoryClient;

    //序列化为Json字符串
    private final ObjectMapper MAPPER = new ObjectMapper();



    //elasticsearch响应前台请求
    public SearchResult queryGoods(SearchRequset searchRequset) {

        //判断搜索条件
        if(searchRequset.getKey()==null){
            return null;
        }
        //自定义查询构造器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //此时要把MatchQueryBuilder替换成 bool进行过滤操作
        BoolQueryBuilder boolQueryBuilder = buildBoolQueryBuilder(searchRequset);

        //添加查询条件（根据key查询）
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);

        //分页 默认是第一页 每页数量固定 页号从零开始
        nativeSearchQueryBuilder.withPageable(PageRequest.of(searchRequset.getPage()-1,searchRequset.getSize()));

        //过滤  对于手机展示我们只需要 id和subTitle
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));

        //根据分类id和品牌id聚合 得到所有品牌和分类
        String categoriesAggName="categories";
        String brandsAggName="brands";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoriesAggName).field("cid3"));
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandsAggName).field("brandId"));

        //提交查询
        AggregatedPageImpl search = (AggregatedPageImpl)this.goodsRespository.search(nativeSearchQueryBuilder.build());

        //得到两个聚合（强转成可得桶）
        MultiBucketsAggregation categoriesAgg = (MultiBucketsAggregation)search.getAggregation(categoriesAggName);
        MultiBucketsAggregation brandsAgg = (MultiBucketsAggregation)search.getAggregation(brandsAggName);

        //得到结果
        List<Map<String,Object>> categories = getCategoriesAggResult(categoriesAgg);
        List<Brand> brands = getBrandsAggResult(brandsAgg);

        //定义规格参数容器
        List<Map<String, Object>> specs = null;

        //根据cid3,并且根据规格参数聚合
        // （首先判断，只有分类是一个才应该出现规格参数）
        if (!CollectionUtils.isEmpty(categories) && categories.size()==1){
            specs=getAggParams(Long.valueOf( (String) (categories.get(0).get("id")) ),boolQueryBuilder);
        }
        //System.out.println("  ----------------n"+specs);
        //返回结果  (总数据量，总页数，内容)
        return new SearchResult(search.getTotalElements(),search.getTotalPages(),search.getContent(),categories,brands,specs);
    }

    //构建 过滤规格参数品牌等字段搜索
    private BoolQueryBuilder buildBoolQueryBuilder(SearchRequset searchRequset) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //基本查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("all",searchRequset.getKey()).operator(Operator.AND));

        //过滤
        if (CollectionUtils.isEmpty(searchRequset.getFilter())){
            return boolQueryBuilder;
        }
        for (Map.Entry<String, String> entry : searchRequset.getFilter().entrySet()) {

            String key = entry.getKey();
            // 如果过滤条件是“品牌”, 过滤的字段名：brandId
            if (StringUtils.equals("品牌", key)) {
                key = "brandId";
            } else if (StringUtils.equals("分类", key)) {
                // 如果是“分类”，过滤字段名：cid3
                key = "cid3";
            } else {
                // 如果是规格参数名，过滤字段名：specs.key.keyword
                key = "specs." + key + ".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;
    }

    //聚合查询参数
    private List<Map<String, Object>> getAggParams(Long id, QueryBuilder query) {

        //聚合结果容器
        List<Map<String, Object>> aggParams = new ArrayList<>();

        NativeSearchQueryBuilder queryBuilders = new NativeSearchQueryBuilder();
        //我们只聚合搜索字段
        //所以查询哪些是聚合字段
        List<SpecParam> params = this.specificationClient.querySearchParamsByCid(id, true);

        //添加查询条件
        //你肯定只聚合查询到的  对吧
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //添加查询条件（根据key查询）（过滤参数阶段改成了 boolQueryBuilder）
        nativeSearchQueryBuilder.withQuery(query);

        //给每个字段添加聚合
        params.forEach(param->{
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });

        //过滤结果集，
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));

        //执行查询
        //强转结果集
        AggregatedPageImpl<Goods>search = (AggregatedPageImpl<Goods>)this.goodsRespository.search(nativeSearchQueryBuilder.build());

        //直接转成map形式  <聚合名称，聚合结果集>
        Map<String, Aggregation> aggregationMap = search.getAggregations().asMap();

        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("name",entry.getKey());

            //得到聚合结果集
            MultiBucketsAggregation value = (MultiBucketsAggregation)entry.getValue();

            //规格参数值结果集容器
            List options = new ArrayList<String>();
            value.getBuckets().forEach(bucket->{
                //把结果放入options
                options.add(bucket.getKeyAsString());
            });
            //放入map
            map.put("options",options);
            aggParams.add(map);
        }
        return aggParams;
    }


    //解析品牌聚合
    private List<Brand> getBrandsAggResult(MultiBucketsAggregation brandsAgg) {


        //stream流 直接返回一个list集合
        return brandsAgg.getBuckets().stream().map(bucket->{

            //得到聚合结果 也就是 id
            String keyAsString = bucket.getKeyAsString();
           // System.out.println(keyAsString+"-----------------------"+Integer.valueOf(keyAsString).longValue());

            //查询品牌 并作为list内的一个成员返回
            return this.brandClient.queryBrandById(Integer.valueOf(keyAsString).longValue());

        }).collect(Collectors.toList());
    }


    //解析分类聚合
    private List<Map<String, Object>> getCategoriesAggResult(MultiBucketsAggregation categoriesAgg) {
        return categoriesAgg.getBuckets().stream().map(bucket->{
            HashMap<String, Object> map = new HashMap<>();
            //分类 3id
            String cid3 = bucket.getKeyAsString();
//            System.out.println(cid3+"------------------SearchService");
            //分类名称
            List<String> name =  this.categoryClient.queryNamesByIds(Arrays.asList(Long.valueOf(cid3)));
//            System.out.println(name+"-----------------------------");
            map.put("id",cid3);
            //根据cid3查询  所以name也就只有一个
            map.put("name",name.get(0));
            return map;
        }).collect(Collectors.toList());
    }


    //goods
    public Goods buildGoods(SpuBo spuBo) throws JsonProcessingException {

        Goods goods = new Goods();
//        ！索尼1300万摄像头！<a href='https://sale.jd.com/act/DhKrOjXnFcGL.html'
//        target='_blank'>华为新品全面上线，更多优惠猛戳》》</a>', saleable=true, valid=true,
//        createTime=Sat Apr 21 15:55:15 GMT+08:00 2018,
//        lastUpdateTime=Fri Oct 09 18:30:49 GMT+08:00 2020}

        //spuBo中已有且可以直接拷贝的字段：id subtitle cid(1,2,3) 创建时间
        BeanUtils.copyProperties(spuBo,goods);
//        System.out.println(spuBo.getBname());
//        System.out.println(spuBo.getTitle());
//        System.out.println(spuBo.getCname());
//        System.out.println(spuBo.getCreateTime());
//        Brand brand = this.brandClient.queryBrandById(spuBo.getBrandId());
        //title  品牌名称 分类名称 这三个是搜索字段（All）的组成；spuBo里面有
        // 分类名称当时合成字符串是用/合成，
        //System.out.println(StringUtils.replace(spuBo.getCname(), "/", " "));
        goods.setAll(spuBo.getTitle()+" "+StringUtils.replace(spuBo.getCname(),"/"," ")+" "+spuBo.getBname());
        //System.out.println("goods.getAll->"+goods.getAll());

        //得到sku 获得 prices和skus
        List<Sku> skus = goodsClient.querySkuBySpuId(spuBo.getId());

        ArrayList<Long> prices = new ArrayList<>();

        //goods中的skus并不不要很多东西，
        //可以弄个马Map，
        //key->所用字段名，  value->字段对应值
        ArrayList<Map<String,Object>> mapArrayList = new ArrayList<>();
        //System.out.println("skus.sout->------------------------");
        skus.forEach(sku -> {
            //System.out.println("sku.getPrice()"+sku.getPrice()+sku.getId()+sku.getTitle()+sku.getPrice()+sku.getImages());
            prices.add(sku.getPrice());//价格集合
            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put("id",sku.getId());
            hashMap.put("title",sku.getTitle());
            hashMap.put("price",sku.getPrice());
            hashMap.put("image",StringUtils.isBlank(sku.getImages())?"":sku.getImages());
            mapArrayList.add(hashMap);
        });
        //System.out.println("<-skus.sout OVER------------------");
        goods.setPrice(prices);//设置价格
        goods.setSkus(MAPPER.writeValueAsString(mapArrayList));//设置skus


        //最后一个 Map<String, Object> specs;  可搜索的规格参数，key是参数名，值是参数值
        HashMap<String,Object> hasMap = new HashMap<>();

        //先根据cid得到所有参数记录，也就是所有的key有了。
        List<SpecParam> specParams = this.specificationClient.querySpecParamByCid(spuBo.getCid3());

        //去tb_spu_detail根据spuId得到需要的字段，也就是specs的值
        SpuDetail spuDetail = this.goodsClient.querySpuDetailById(spuBo.getId());

        String genericSpec = spuDetail.getGenericSpec();
        //System.out.println("genericSpec------------start");
        //System.out.println(genericSpec);
        //反序列化为Map类型，方便取值
        // 比如：
        //     {"1":"华为（HUAWEI）","2":"华为麦芒5（HUAWEI MLA-AL10）","3":2016,
        //     "5":160,"6":"其它","7":"Android","8":"骁龙（Snapdragon)",
        //     "9":"骁龙625（MSM8953）","10":"八核","11":2,"14":5.5,
        //     "15":"1920*1080(FHD)","16":800,"17":1600,"18":3340}
        //       1，2，3，5，，，就可以直接作为key,value就是后面的值
        Map<Long, Object> generricSpecMap = MAPPER.readValue(genericSpec, new TypeReference<Map<Long, Object>>() {});
        //System.out.println("generricSpecMap------------start");
        //generricSpecMap.forEach((a,b)->{
        //    System.out.println(a+" "+b);
        //});
        String specialSpec = spuDetail.getSpecialSpec();
        //同上，知识value是list,因为都是待选的
        Map<Long, List<Object>> specialSpecMap = MAPPER.readValue(specialSpec, new TypeReference<Map<Long, List<Object>>>() {});

        specParams.forEach(specParam -> {

            //判断规格参数是否通用 specParam.getGeneric()为 1 是通用
            if (specParam.getGeneric()){
                //通用我就去 generic字段找我要的信息
                //System.out.println("specParam.getId()+specParam.getName()"+specParam.getId()+specParam.getName());
                //stem.out.println("generricSpecMap.get(specParam.getId())"+generricSpecMap.get("1"));
                String value = generricSpecMap.get(specParam.getId()).toString();

                //判断是不是数字
                if(specParam.getNumeric()){

                    value=chooseSegment(value,specParam);
                }
                hasMap.put(specParam.getName(),value);

            }else {
                //否则就去special字段
                hasMap.put(specParam.getName(),specialSpecMap.get(specParam.getId()));
            }
        });
        goods.setSpecs(hasMap);

        return goods;
    }
    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

}
